﻿using System;
using System.Collections.Generic;
using FlitBit.Core;
using FlitBit.Core.Parallel;

namespace FlitBit.Cache
{
	/// <summary>
	///   Default eviction monitor implementation.
	/// </summary>
	/// <typeparam name="TKey">key type K</typeparam>
	public class ReactiveCacheEvictionMonitor<TKey> : Disposable, ICacheEvictionMonitor
	{
		Reactor<TKey> _reactor;

		/// <summary>
		///   Creates a new instance.
		/// </summary>
		/// <param name="contextKeys">context keys to be monitored</param>
		/// <param name="reactor">reactor method that recieves eviction notification</param>
		public ReactiveCacheEvictionMonitor(IEnumerable<string> contextKeys, Action<Reactor<TKey>, TKey> reactor)
		{
			_reactor = new Reactor<TKey>(reactor);
			RegistrationKey = Guid.NewGuid();
			ContextKeys = contextKeys.ToReadOnly();
		}

		/// <summary>
		///   Invoked when unrecognized eviction info is received.
		/// </summary>
		/// <param name="contextKey"></param>
		/// <param name="evictionInfo"></param>
		protected virtual void HandleUnrecognizedEvictionInfo(string contextKey, object evictionInfo)
		{}

		/// <summary>
		///   Performs disposal for the instance.
		/// </summary>
		/// <param name="disposing"></param>
		/// <returns></returns>
		protected override bool PerformDispose(bool disposing)
		{
			_reactor.Cancel();
			Util.Dispose(ref _reactor);
			return disposing;
		}

		#region ICacheEvictionMonitor Members

		/// <summary>
		///   Gets the monitor's registration key.
		/// </summary>
		public Guid RegistrationKey { get; private set; }

		/// <summary>
		///   Gets the context keys identifying what the monitor is
		///   monitoring.
		/// </summary>
		public IEnumerable<string> ContextKeys { get; private set; }

		/// <summary>
		///   Invoked by the framework when cache evictions arrive.
		/// </summary>
		/// <param name="contextKey">a context key</param>
		/// <param name="evictionInfo">the eviction info</param>
		public void OnEvictionNotification(string contextKey, object evictionInfo)
		{
			if (contextKey == null)
			{
				return;
			}

			var keys = evictionInfo as IEnumerable<TKey>;
			if (keys != null)
			{
				foreach (var k in keys)
				{
					_reactor.Push(k);
				}
			}
			else if (evictionInfo is TKey)
			{
				_reactor.Push((TKey) evictionInfo);
			}
			else
			{
				HandleUnrecognizedEvictionInfo(contextKey, evictionInfo);
			}
		}

		#endregion
	}
}